package lm.spring.feign.support;

import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.Assert;

import java.util.Map;
import java.util.Set;

/**
 * 注册feign client beans
 *
 * @author liangming 2021-12-25 0:03
 */
public class FeignRegistry implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

    private static final org.slf4j.Logger log = LoggerFactory.getLogger(FeignRegistry.class);

    private BeanNameGenerator beanNameGenerator = new AnnotationBeanNameGenerator();

    private ResourceLoader resourceLoader;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        if (!importingClassMetadata.hasAnnotation(EnableFeignClients.class.getName())) {
            return;
        }

        long start = System.currentTimeMillis();

        Map<String, Object> annotationAttributesMap = importingClassMetadata.getAnnotationAttributes(EnableFeignClients.class.getName());
        AnnotationAttributes attributes = AnnotationAttributes.fromMap(annotationAttributesMap);
        String[] pkgs = retrievePackageNames(importingClassMetadata, attributes);

        FeignClientScanner scanner = new FeignClientScanner(registry, false);
        scanner.setResourceLoader(resourceLoader);
        scanner.registerFilters();

        // 扫描包
        for (String needScanPackage : pkgs) {
            Set<BeanDefinition> candidateComponents = scanner.findCandidateComponents(needScanPackage);
            for (BeanDefinition candidateComponent : candidateComponents) {
                if (candidateComponent instanceof AnnotatedBeanDefinition) {
                    AnnotatedBeanDefinition annotatedBeanDefinition = (org.springframework.beans.factory.annotation.AnnotatedBeanDefinition) candidateComponent;
                    AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata();
                    Assert.isTrue(annotationMetadata.isInterface(), "@FeignClient can only be specified on an interface");

                    try {
                        String className = annotationMetadata.getClassName();
                        Class<?> aFeignClass = Class.forName(className);
                        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(FeignFactoryBean.class)
                                .addPropertyValue("objectType", aFeignClass)
                                .setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
                                .setRole(BeanDefinition.ROLE_INFRASTRUCTURE)
                                .getBeanDefinition();

                        String beanName = this.beanNameGenerator.generateBeanName(annotatedBeanDefinition, registry);
                        registry.registerBeanDefinition(beanName, beanDefinition);
                    } catch (ClassNotFoundException e) {
                        log.error(e.getMessage(), e);
                    }
                }
            }
        }

        log.debug("创建feign对象耗时(ms)：" + (System.currentTimeMillis() - start));
    }

    /**
     * 获取feign client扫描路径
     *
     * @param importingClassMetadata
     * @param attributes
     * @return
     */
    private String[] retrievePackageNames(AnnotationMetadata importingClassMetadata, AnnotationAttributes attributes) {
        String[] basePackages = attributes.getStringArray("basePackages");
        if (basePackages.length > 0) {
            return basePackages;
        }

        String className = importingClassMetadata.getClassName();
        int lastDot = className.lastIndexOf(".");
        return new String[]{className.substring(0, lastDot)};
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    class FeignClientScanner extends ClassPathBeanDefinitionScanner {

        FeignClientScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
            super(registry, useDefaultFilters);
        }

        void registerFilters() {
            addIncludeFilter(new AnnotationTypeFilter(FeignClient.class));
        }

        @Override
        protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
            return super.doScan(basePackages);
        }

        @Override
        protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
            boolean isCandidate = false;
            if (beanDefinition.getMetadata().isIndependent()) {
                if (!beanDefinition.getMetadata().isAnnotation()) {
                    isCandidate = true;
                }
            }
            return isCandidate;
        }
    }
}
